﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion


#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using Microsoft.Surface.Presentation.Controls.Primitives;
using ScrumTable.Common.Logging;
using ScrumTable.UI.View.Controls;
using ScrumTable.UI.View.CustomControl;
using ScrumTable.UI.View.Extension;
using ScrumTable.UI.View.Util;
using ScrumTable.UI.View.WinTouch.Util;
using ScrumTable.UI.ViewModel;

#endregion

namespace ScrumTable.UI.View.WinTouch.UserControls
{
    /// <summary>
    /// Interaction logic for SurfaceDragDropListBox.xaml
    /// </summary>
    public partial class SurfaceDragDropListBox : SurfaceListBox, IDropUserControl
    {
        private const double DRAGGED_ELEMENT_OPACITY_MULTIPLIER = 0.5;
        private const int HOLD_TIMESPAN_DRAG_MILLISECOND = 500;
        private const int CONTACT_MOVE_DISTANCE_TO_STOP_TIMER = 15;
        private const int CONTACT_MOVE_DISTANCE_START_DRAG = 20;
        private const int CONTACT_MOVE_DISTANCE_OPPOSITE_DIRECTION_TO_STOP = 15;
        private SurfaceScrollViewer _scrollViewer;

        #region Properties

        public Color EffectColor
        {
            set
            {
                Resources["EffectColor"] = value;
            }
        }

        private SurfaceScrollViewer SurfaceScrollViewer
        {
            get
            {
                if (_scrollViewer == null)
                {
                    _scrollViewer = ViewElementUtils.FindVisualChild<SurfaceScrollViewer>(this);
                }
                return _scrollViewer;
            }
        }

        #region IsScrollVisible
        /// <summary>
        /// DependencyProperty for IsScrollVisible.
        /// </summary>
        public static readonly DependencyProperty IsScrollVisibleProperty =
            DependencyProperty.Register("IsScrollVisible",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(false));
        /// <summary>
        /// Gets or sets if the ScrollViewer is currently visible.
        /// </summary>
        /// <value>If the ScrollViewer is visible.</value>
        public bool IsScrollVisible
        {
            get { return (bool)GetValue(IsScrollVisibleProperty); }
            set { SetValue(IsScrollVisibleProperty, value); }
        }

        #endregion
        #region IsItemSelectable
        /// <summary>
        /// DependencyProperty for IsItemSelectable.
        /// </summary>
        public static readonly DependencyProperty IsItemSelectableProperty =
            DependencyProperty.Register("IsItemSelectable",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(true));
        /// <summary>
        /// Gets or sets if items can be selected by clicking them.
        /// </summary>
        /// <value>If items can be selected.</value>
        public bool IsItemSelectable
        {
            get { return (bool)GetValue(IsItemSelectableProperty); }
            set { SetValue(IsItemSelectableProperty, value); }
        }

        #endregion

        #region ShowSelection
        /// <summary>
        /// DependencyProperty for IsItemSelectable.
        /// </summary>
        public static readonly DependencyProperty ShowSelectionProperty =
            DependencyProperty.Register("ShowSelection",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(true));
        /// <summary>
        /// Gets or sets if items can be selected by clicking them.
        /// </summary>
        /// <value>If items can be selected.</value>
        public bool ShowSelection
        {
            get { return (bool)GetValue(ShowSelectionProperty); }
            set { SetValue(ShowSelectionProperty, value); }
        }

        #endregion

        #region CanDrag
        /// <summary>
        /// DependencyProperty for IsItemSelectable.
        /// </summary>
        public static readonly DependencyProperty CanDragProperty =
            DependencyProperty.Register("CanDrag",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(true));
        /// <summary>
        /// Gets or sets if items can be dragged
        /// </summary>
        /// <value>If items can dragged</value>
        public bool CanDrag
        {
            get { return (bool)GetValue(CanDragProperty); }
            set { SetValue(CanDragProperty, value); }
        }
        #endregion

        #region DragOnMove
        /// <summary>
        /// DependencyProperty for DragOnMove.
        /// </summary>
        public static readonly DependencyProperty DragOnMoveProperty =
            DependencyProperty.Register("DragOnMove",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(true));
        /// <summary>
        /// Gets or sets if the Item should be dragged when Contact is moved.
        /// </summary>
        /// <value>If item should be dragged.</value>
        public bool DragOnMove
        {
            get { return (bool)GetValue(DragOnMoveProperty); }
            set { SetValue(DragOnMoveProperty, value); }
        }
        #endregion
        #region DragOnMoveOnlyIfOppositeOrientation
        /// <summary>
        /// DependencyProperty for DragOnMoveOnlyIfOppositeOrientation.
        /// </summary>
        public static readonly DependencyProperty DragOnMoveOnlyIfOppositeOrientationProperty =
            DependencyProperty.Register("DragOnMoveOnlyIfOppositeOrientation",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata( false ));
        /// <summary>
        /// Gets or sets if the item should only be dragged when the move happens to be opposite to the
        /// orientation of the SurfaceListBox.
        /// </summary>
        /// <value>If item should be dragged only when move is opposite to orientation.</value>
        public bool DragOnMoveOnlyIfOppositeOrientation
        {
            get { return (bool)GetValue(DragOnMoveOnlyIfOppositeOrientationProperty); }
            set { SetValue(DragOnMoveOnlyIfOppositeOrientationProperty, value); }
        }
        #endregion

        #region Orientation
        /// <summary>
        /// DependencyProperty for Orientation.
        /// </summary>
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation",
                                        typeof(Orientation),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(Orientation.Vertical));
        /// <summary>
        /// Gets or sets the Orientation of the SurfaceListBox.
        /// </summary>
        /// <value>The Orientation.</value>
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }
        #endregion


        #region ChildTypeAsDragDropSource
        /// <summary>
        /// DependencyProperty for ChildTypeAsDragDropSource.
        /// </summary>
        public static readonly DependencyProperty ChildTypeAsDragDropSourceProperty =
            DependencyProperty.Register("ChildTypeAsDragDropSourceProperty",

                                        typeof(IList<Type>),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(null));
        /// <summary>
        /// if this value is set, child objects on the ListBoxItem can be moved seperately. The ListBoxItem itself can't be moved. 
        /// </summary>
        /// <value>a child type on the ListBoxItem which can be moved separately .</value>
        public IList<Type> ChildTypeAsDragDropSource
        {
            get { return (IList<Type>)GetValue(ChildTypeAsDragDropSourceProperty); }
            set { SetValue(ChildTypeAsDragDropSourceProperty, value); }
        }
        #endregion


        #region AllowedDropType
        /// <summary>
        /// DependencyProperty for AllowedDropType.
        /// </summary>
        public static readonly DependencyProperty AllowedDropTypeProperty =
            DependencyProperty.Register("AllowedDropType",
                                        typeof(Type),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(null));
        /// <summary>
        /// Gets or sets which type is allowed to be dropped into this ListBox (will be added automatically.
        /// </summary>
        /// <value>The allowed Type to drop here.</value>
        public Type AllowedDropType
        {
            get { return (Type)GetValue(AllowedDropTypeProperty); }
            set { SetValue(AllowedDropTypeProperty, value); }
        }
        #endregion

        #region ItemsAreStatic
        /// <summary>
        /// DependencyProperty for ItemsAreStatic.
        /// </summary>
        public static readonly DependencyProperty ItemsAreStaticProperty =
            DependencyProperty.Register("ItemsAreStatic",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(false));
        /// <summary>
        /// Gets or sets if the Items will stay when dragged and can be dragged many times at the same time.
        /// </summary>
        /// <value>If items are static.</value>
        public bool ItemsAreStatic
        {
            get { return (bool)GetValue(ItemsAreStaticProperty); }
            set { SetValue(ItemsAreStaticProperty, value); }
        }
        #endregion

        #region MultipleColumnsRows
        /// <summary>
        /// DependencyProperty for MultipleColumnsRows.
        /// </summary>
        public static readonly DependencyProperty MultipleColumnsRowsProperty =
            DependencyProperty.Register("MultipleColumnsRows",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(false, new PropertyChangedCallback(SurfaceDragDropListBox.OnMultipleColumnsRowsChanged)));
        /// <summary>
        /// Gets or sets if the listbox acts as a WrapPanel (true) or StackPanel (false).
        /// </summary>
        /// <value>If there are multiple columns and rows.</value>
        public bool MultipleColumnsRows
        {
            get { return (bool)GetValue(MultipleColumnsRowsProperty); }
            set
            {
                SetValue(MultipleColumnsRowsProperty, value);
            }
        }

        #endregion


        #region MultipleColumnsRowsWidth
        /// <summary>
        /// DependencyProperty for MultipleColumnsRowsWidth.
        /// </summary>
        public static readonly DependencyProperty MultipleColumnsRowsSizeProperty =
            DependencyProperty.Register("MultipleColumnsRowsSize",
                                        typeof(Size),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(Size.Empty));
        /// <summary>
        /// Gets or sets the length of one row, if this value is not set the list trys to figure out the size
        /// </summary>
        /// <value>If there are multiple columns and rows.</value>
        public Size MultipleColumnsRowsSize
        {
            get { return (Size)GetValue(MultipleColumnsRowsSizeProperty); }
            set
            {
                SetValue(MultipleColumnsRowsSizeProperty, value);
            }
        }

        #endregion


        #region AddOnDrop
        /// <summary>
        /// DependencyProperty for AddOnDrop.
        /// </summary>
        public static readonly DependencyProperty AddOnDropProperty =
            DependencyProperty.Register("AddOnDrop",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(true));
        /// <summary>
        /// Gets or sets if the dropped item will be automatically added to the list behind.
        /// </summary>
        /// <value>If items are added on drop.</value>
        public bool AddOnDrop
        {
            get { return (bool)GetValue(AddOnDropProperty); }
            set { SetValue(AddOnDropProperty, value); }
        }
        #endregion
        #region RemoveOnDragComplete
        /// <summary>
        /// DependencyProperty for RemoveOnDragComplete.
        /// </summary>
        public static readonly DependencyProperty RemoveOnDragCompleteProperty =
            DependencyProperty.Register("RemoveOnDragComplete",
                                        typeof(bool),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(false));
        /// <summary>
        /// Gets or sets if an item which has been draged should be removed after drag complete.
        /// </summary>
        /// <value>If items are removed after drag complete.</value>
        public bool RemoveOnDragComplete
        {
            get { return (bool)GetValue(RemoveOnDragCompleteProperty); }
            set { SetValue(RemoveOnDragCompleteProperty, value); }
        }


        /// <summary>
        /// DependencyProperty for RemoveOnDragComplete.
        /// </summary>
        public static readonly DependencyProperty CanDragDropProperty =
            DependencyProperty.Register("CanDragDrop",
                                        typeof(Func<object, CanDropResult>),
                                        typeof(SurfaceDragDropListBox),
                                        new UIPropertyMetadata(null));
 
        /// <summary>
        /// Gets or sets the handle if a item can be dropped on this list
        /// </summary>
        /// <value>If items are removed after drag complete.</value>
        public Func<object, CanDropResult> CanDragDrop
        {
            get { return (Func<object, CanDropResult>)GetValue(CanDragDropProperty); }
            set { SetValue(CanDragDropProperty, value); }
        }


        #endregion

        #endregion

        #region Fields

        /// <summary>
        /// Saving the currently captured Contacts.
        /// Key: ID of the Contact
        /// Value: Contact of the first Contact
        /// </summary>
        private readonly Dictionary<int, FirstContactInfo> _contactStartPoints = new Dictionary<int, FirstContactInfo>();
        private readonly Dictionary<int, DispatcherTimer> _runningTimers = new Dictionary<int, DispatcherTimer>();
        private readonly List<Control> _draggedObjects = new List<Control>();

        #endregion

        #region Constructors / Destructor

        /// <summary>
        /// Initializes a new instance of the <see cref="SurfaceDragDropListBox"/> class.
        /// </summary>
        public SurfaceDragDropListBox()
        {
            InitializeComponent();
            
            base.AddHandler(ScrollViewer.ScrollChangedEvent, new RoutedEventHandler(this.OnScrollChanged));
            /*
            PreviewContactUp += OnSurfaceDragDropListBoxPreviewContactUp;
            PreviewContactChanged += OnSurfaceDragDropListBoxPreviewContactChanged;
            */
            SurfaceDragDrop.AddDragCompletedHandler(this, OnDragCompleteHandler);
            SurfaceDragDrop.AddDragCanceledHandler(this, OnDragCanceledHandler);
            SurfaceDragDrop.AddPreviewQueryTargetHandler(this, OnQueryTargetHandler);
            SurfaceDragDrop.AddDropHandler(this, OnDropHandler);
            SelectionChanged += OnSelectionChanged;
            this.Loaded += new RoutedEventHandler(SurfaceDragDropListBox_Loaded);
          //  Background = Brushes.Red;
            SetUpBinding();
            this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
            
        }

        void SurfaceDragDropListBox_Loaded(object sender, RoutedEventArgs e)
        {
                /*
            if (SurfaceScrollViewer != null)
            {
                Background = Brushes.Red;
                this.SurfaceScrollViewer.Background = null;
                ViewElementUtils.FindVisualChild<Grid>(SurfaceScrollViewer).Background = Brushes.Transparent;

            }*/
        }

        private void SetUpBinding()
        {
            /* add this line if you want :)
            Binding scrollVisibleBinding = new Binding("IsVisible")
                                               {
                                                   RelativeSource = RelativeSource.Self,
                                                   Path = new PropertyPath("IsScrollVisible")
                                               };
             */ 
            //SetBinding(DragOnMoveOnlyIfOppositeOrientationProperty, scrollVisibleBinding);
        }

        private void InputDown(InputEventArgs e, Point pos)
        {
            UpdateIsScrollVisible();

            //Workaround...needs if in the group header is an other droplistbox!
            if (Parent == null) { return; }

            var contactInfo = new FirstContactInfo
            {
                Event = e,
                Position = pos
            };

            if (_contactStartPoints.ContainsKey(e.Device.GetHashCode()))
            {
                _contactStartPoints.Remove(e.Device.GetHashCode());
            }
            _contactStartPoints.Add(e.Device.GetHashCode(), contactInfo);

            StartTimerForEvent(e);
        }

        private void InputMove(InputEventArgs e, Point pos)
        {

            var id = e.Device.GetHashCode();
            if (!_contactStartPoints.ContainsKey(id)) return;
            Point p1 = _contactStartPoints[id].Position;
            Point p2 = pos;
            double distance = PositionUtils.CalculateDistance(p1, p2);
            if (distance > CONTACT_MOVE_DISTANCE_TO_STOP_TIMER)
                StopTimer(e.Device.GetHashCode());

            if (DragOnMove)
            {
                double distanceHorizontal = Math.Abs(p1.X - p2.X);
                double distanceVertical = Math.Abs(p1.Y - p2.Y);

                if (DragOnMoveOnlyIfOppositeOrientation)
                    DoOrientationMove(id, Orientation, distanceHorizontal, distanceVertical);
                else
                {
                    if (e.OriginalSource is SurfaceThumb) //if focus is on a scrollthumb -> take longer distance befor scrolling
                    {
                        if (distance > CONTACT_MOVE_DISTANCE_START_DRAG*8)
                            StartDrag(id, false);
                    }
                    else
                    {
                        if (distance > CONTACT_MOVE_DISTANCE_START_DRAG)
                            StartDrag(id, false);
                    }
                    
                }
            }
        }


        #endregion

        #region Overrides


        protected override DependencyObject GetContainerForItemOverride()
        {
            var item =  base.GetContainerForItemOverride();
            if (!ShowSelection && item is SurfaceListBoxItem)
            {
                ((SurfaceListBoxItem)item).Style = this.Resources["NoSelectionStyle"] as Style;
            }
            return item;
        }

        protected override void OnPreviewTouchDown(TouchEventArgs e)
        {
            base.OnPreviewTouchDown(e);
            InputDown(e, e.GetTouchPoint(this).Position);
        }

      
        protected override void OnPreviewTouchUp(TouchEventArgs e)
        {
            base.OnPreviewTouchUp(e);
            StopTimer(e.Device.GetHashCode());
        }

        protected override void OnPreviewTouchMove(TouchEventArgs e)
        {
            base.OnPreviewTouchMove(e);
            InputMove(e, e.GetTouchPoint(this).Position);
        }


        protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseDown(e);
            UpdateIsScrollVisible();
            InputDown(e, e.GetPosition(this));
        }


        protected override void OnPreviewMouseMove(MouseEventArgs e)
        {
            base.OnPreviewMouseMove(e);
            InputMove(e, e.GetPosition(this));
        }

        protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
        {
            base.OnPreviewMouseUp(e);
            if (_contactStartPoints.ContainsKey(e.Device.GetHashCode()))
            {
                _contactStartPoints.Remove(e.Device.GetHashCode());
            }
            StopTimer(e.Device.GetHashCode());
        }

        #endregion

        #region Methods

        private Orientation GetOpposite()
        {
            return (Orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal;
        }

        #region HoldTimerEvents
        private void StartTimerForEvent(InputEventArgs e)
        {
            if (!_runningTimers.ContainsKey(e.Device.GetHashCode()))
            {
                var t1 = new DispatcherTimer {Interval = new TimeSpan(0, 0, 0, 0, HOLD_TIMESPAN_DRAG_MILLISECOND)};
                t1.Tick += ContactHoldTick;
                t1.Start();
                _runningTimers[e.Device.GetHashCode()] = t1;
            }
        }

        private void StopTimer(int key)
        {
            if (!_runningTimers.ContainsKey(key)) return;

            _runningTimers[key].Stop();
            _runningTimers.Remove(key);
        }

        void ContactHoldTick(object sender, EventArgs e)
        {
            var timer = sender as DispatcherTimer;
            var contactId = FindContactIdForTimer(timer);

            if (contactId != 0)
                StopTimer(contactId);

            StartDrag(contactId, true);
        }


        /// <summary>
        /// Finds the contact id for timer.
        /// </summary>
        /// <param name="timer">The timer.</param>
        /// <returns>Contact ID for the given timer. Returns 0 if timer was not found.</returns>
        private int FindContactIdForTimer(DispatcherTimer timer)
        {
            int contactId = 0;
            foreach (var key in _runningTimers.Keys)
            {
                if (_runningTimers[key].Equals(timer))
                {
                    contactId = key;
                    break;
                }
            }
            return contactId;
        }

        #endregion

        private void StartDrag(int contactId, bool dragFromHoldingTimer)
        {
            if (!CanDrag)
            {
                return;
            }

            if(!_contactStartPoints.ContainsKey( contactId))
            {
                return;
            }
            var e = _contactStartPoints[contactId].Event;
            _contactStartPoints.Remove(contactId);

            // Find the touched SurfaceListBoxItem object.
            SurfaceListBoxItem draggedElement = ViewElementUtils.FindVisualParent<SurfaceListBoxItem>(e.OriginalSource);

            if (draggedElement == null)
            {
                return;
            }
            
            //Workaround: If an user clicks opens an element with clicking on it -> the drag operation does not end...
            if( dragFromHoldingTimer  && e.Device.Target != draggedElement)
            {   
                return;
            }
            ContentControl cursorVisual=null;
            Control draggedChildElement=null;
            if(  ChildTypeAsDragDropSource != null)
            {
                //Get the cursor and the element for a child element. 
                draggedChildElement = GetDraggedChildElement(e, ref cursorVisual);    
                if( draggedChildElement == null )
                {
                    //no child found -> leave
                    return; 
                }
            }
            
            
            if (_draggedObjects.Contains(draggedElement))
                return;

            if (_draggedObjects.Contains(draggedChildElement))
                return;

            if( cursorVisual == null)
            {
                cursorVisual = new ContentControl
                {
                    Content = draggedElement.DataContext,
                    ContentTemplate = ItemTemplate                    
                };
            }            

            // Add a handler. This will enable the application to change the visual cues.
            SurfaceDragDrop.AddTargetChangedHandler(cursorVisual, OnTargetChanged);

            // Create a list of input devices. Add the contacts that
            // are currently captured within the dragged element and
            // the current contact (if it isn't already in the list).
            var devices = new List<InputDevice> { e.Device };
            devices.AddRange(draggedElement.TouchesCapturedWithin.Where(contact => contact != e.Device).Cast<InputDevice>());
            
            
            // Get the drag source object
            ItemsControl dragSource = ItemsControlFromItemContainer(draggedElement);

            if (dragSource == null) return;

            if (!DrapDropUtil.IsContactActive(e))
                return;


            //cursorVisual.RenderTransformOrigin = new Point(0.5,0.5);
            //cursorVisual.RenderTransform = new ScaleTransform(){ ScaleX = 0.2, ScaleY = 0.2};
            cursorVisual.Opacity = 0.5;
            try
            {
                var cursor =
                    SurfaceDragDrop.BeginDragDrop(
                        draggedChildElement ?? draggedElement,                 // The SurfaceListBox object that the cursor is dragged out from.
                        draggedChildElement ?? draggedElement,            // The SurfaceListBoxItem object that is dragged from the drag source.
                        cursorVisual,               // The visual element of the cursor.
                        (draggedChildElement != null) ? draggedChildElement.DataContext : draggedElement.DataContext, // The data associated with the cursor.
                        devices,                    // The input devices that start dragging the cursor.
                        DragDropEffects.Move);      // The allowed drag-and-drop effects of the operation.
                
                if (!ItemsAreStatic)
                {
                    _draggedObjects.Add(draggedChildElement ?? draggedElement);
                    (draggedChildElement ?? draggedElement).Opacity *= DRAGGED_ELEMENT_OPACITY_MULTIPLIER;
                }
                
                // If the drag began successfully, set e.Handled to true. 
                // Otherwise SurfaceListBoxItem captures the contact 
                // and causes the drag operation to fail.
                e.Handled = true;
            }
            catch (Exception exception)
            {
                Logger<SurfaceDragDropListBox>.Info("Starting Drag: Exception", exception);
                OnCancelDrag(draggedElement.DataContext);
            }
        }

        private Control GetDraggedChildElement(InputEventArgs e, ref ContentControl cursorVisual)
        {
            if( ChildTypeAsDragDropSource == null)
            {
                return null;
            }
            Control draggedChildElement = null;
            foreach (Type childType in ChildTypeAsDragDropSource)
            {
                if (ChildTypeAsDragDropSource != null)
                {
                    object draggedElementTmp = ViewElementUtils.FindVisualParent(childType,
                                                                                 e.OriginalSource);
                    if (draggedElementTmp != null)
                    {
                        draggedChildElement = (Control) draggedElementTmp;
                        var control = (Control)Activator.CreateInstance(childType);
                        control.DataContext = draggedChildElement.DataContext;

                        cursorVisual = new ContentControl() {Content = control};
                        return draggedChildElement;
                    }
                }                
            }
            return draggedChildElement;
        }


     
   
        private void UpdateIsScrollVisible()
        {
            var scrollViewer = SurfaceScrollViewer;
            if (scrollViewer == null)
            {
                return;
            }
            IsScrollVisible = scrollViewer.ComputedHorizontalScrollBarVisibility == Visibility.Visible || scrollViewer.ComputedVerticalScrollBarVisibility == Visibility.Visible;
        }

        private void CalcRows(Size size)
        {
            if (!MultipleColumnsRows)
            {
                return;
            }
            var control = ViewElementUtils.FindVisualChild<UserControl>(this);
            var panel = ViewElementUtils.FindVisualChild<VirtualizingMultiLinePanel>(this);


            if ((control == null && MultipleColumnsRowsSize == Size.Empty) || panel == null)
            {
                return;
            }
            Size controlSize = (MultipleColumnsRowsSize == Size.Empty) ? control.DesiredSize : MultipleColumnsRowsSize;

           

            int rows;
            if (Orientation == Orientation.Vertical)
            {
                rows = (int)Math.Floor((size.Width - 40) / controlSize.Width);
            }
            else
            {
                rows = (int)Math.Floor((size.Height - 40) / controlSize.Height);
            }
            if (panel.Rows == rows) { return; }
            panel.Rows = (rows > 0) ? rows : 1;
        }


        #endregion

        #region Events


        private void OnScrollChanged(object sender, RoutedEventArgs args)
        {
            var pannel = ViewElementUtils.FindVisualChild<VirtualizingMultiLinePanel>(this);
            if(pannel == null)
            {
                return;
            }

            var scrollViewerPanel = ViewElementUtils.FindVisualChild<ScrollViewer>(this);
            

            if (args.OriginalSource == scrollViewerPanel)
            {
                
                ScrollChangedEventArgs changedArgs = args as ScrollChangedEventArgs;
                pannel.OnAncestorScrollChanged(sender, changedArgs);
            }
        }


     
        private static void OnMultipleColumnsRowsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var list = ((SurfaceDragDropListBox) (sender));
            FrameworkElementFactory factoryPanel = new FrameworkElementFactory(typeof(VirtualizingMultiLinePanel));
            factoryPanel.SetValue(VirtualizingMultiLinePanel.OrientationProperty, list.Orientation);
            factoryPanel.SetValue(Panel.IsItemsHostProperty, true);
            //factoryPanel.SetValue(VirtualizingMultiLinePanel.RowsProperty, list.MultipleColumnsRows ? 2 : 1);
            var template = new ItemsPanelTemplate { VisualTree = factoryPanel };
            list.ItemsPanel = template;
        
        }

        
         
        private void DoOrientationMove(int id, Orientation notAllowedDirection, double distanceHorizontal, double distanceVertical)
        {
            double inDirectionDistance;
            double inOtherDirectionDistance;

           
            if (notAllowedDirection.Equals(Orientation.Horizontal))
            {
                inDirectionDistance = distanceVertical;
                inOtherDirectionDistance = distanceHorizontal;
            }
            else
            {
                inDirectionDistance = distanceHorizontal;
                inOtherDirectionDistance = distanceVertical;
            }

            if (inDirectionDistance <= CONTACT_MOVE_DISTANCE_START_DRAG) return;

            if (inOtherDirectionDistance < CONTACT_MOVE_DISTANCE_OPPOSITE_DIRECTION_TO_STOP)
                StartDrag(id, false);
            else
                AbortDragOnMoveForContact(id);
        }

        private void AbortDragOnMoveForContact(int id)
        {
            _contactStartPoints.Remove(id);
        }

  

        private static void OnTargetChanged(object sender, TargetChangedEventArgs e)
        {
            // nothing to do here
        }


        private void OnDragCompleteHandler(object sender, SurfaceDragCompletedEventArgs e)
        {
            if (!ItemsAreStatic && (e.Cursor.Effects & DragDropEffects.Move) > 0 && ItemsSource is IList
                && !SourceEqualsTarget(e))
            {
                
                var list = (IList)ItemsSource;
                
                if (RemoveOnDragComplete)
                    list.Remove(e.Cursor.Data);
               

                RemoveDragged(e.Cursor.Data);
                UpdateIsScrollVisible();
            }
            else
            {
                OnCancelDrag(e.Cursor.Data);
            }
        }

        private void OnDragCanceledHandler(object sender, SurfaceDragDropEventArgs e)
        {
            OnCancelDrag(e.Cursor.Data);
        }

        private void OnCancelDrag(object data)
        {
            foreach (var draggedObject in _draggedObjects)
                if (draggedObject.DataContext == data && !ItemsAreStatic)
                    draggedObject.Opacity *= 1 / DRAGGED_ELEMENT_OPACITY_MULTIPLIER;

            RemoveDragged(data);
        }

        private void RemoveDragged(object data)
        {
            Control toDelete = null;
            foreach (var draggedObject in _draggedObjects)
            {
                draggedObject.Opacity = 1;
                if (draggedObject.DataContext == data)
                    toDelete = draggedObject;
            }

            if (toDelete != null)
                _draggedObjects.Remove(toDelete);
        }

        private void OnDropHandler(object sender, SurfaceDragDropEventArgs e)
        {
            if (SourceEqualsTarget(e))
                return;

            if (AddOnDrop && ItemsSource is IList && AllowedDropType != null && AllowedDropType.Equals(e.Cursor.Data.GetType()))
            {
                if (!((IList)ItemsSource).Contains(e.Cursor.Data))
                {
                    ((IList) ItemsSource).Add(e.Cursor.Data);
                }
                else
                {
                    e.Effects = DragDropEffects.None;
                }
                UpdateIsScrollVisible();
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }

        private static bool SourceEqualsTarget(SurfaceDragDropEventArgs e)
        {
            return e.Cursor.CurrentTarget.Equals(e.Cursor.DragSource);
        }

        private static bool SourceEqualsTarget(SurfaceDragCompletedEventArgs e)
        {
            return e.Cursor.CurrentTarget.Equals(e.Cursor.DragSource);
        }

        private void OnQueryTargetHandler(object sender, QueryTargetEventArgs e)
        {
            if (AllowedDropType != null && AllowedDropType.Equals(e.Cursor.Data.GetType()))
            {

            }
            else
            {
                e.UseDefault = true;
                e.ProposedTarget = null;
                e.Handled = false;
            }
        }

        void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (IsItemSelectable) return;

            DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
            timer.Tick += ((tSender, args) =>
            {
                UnselectAll();
                var theTimer = tSender as DispatcherTimer;
                if (theTimer != null) theTimer.Stop();
            });
            timer.Start();


        }



        protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);
            CalcRows(new Size(ActualWidth, ActualHeight));
        }

        
        

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            CalcRows(e.NewSize);
        }
   
        #endregion

        #region Private Classes

        private class FirstContactInfo
        {
            public InputEventArgs Event;
            public Point Position;
        }





        #endregion
		
		#region IDropUserControl Members

        public FrameworkElement GetRootControl
        {
            get { return this; }
        }

        public bool ShowIconWithbackground
        {
            get { return true; }
        }

        public CanDropResult CanDrop(object objectToDrop)
        {
            if (CanDragDrop == null)
            {
                return this.ConvertBoolToEnum(AllowedDropType == null || AllowedDropType.Equals(objectToDrop.GetType()));
            }
            else
            {
                return CanDragDrop(objectToDrop);
            }
        }

        #endregion
    }
}
